/*
尽可能还原 Promise 中的每一个 API, 并通过注释的方式描述思路和原理.
*/

const { values } = require("lodash")

/**
 * MyPromise 接受一个执行器，这个执行器接口两个参数（resolve，reject），并且立即执行
 * MyPromise 有三个状态，pending， fulfilled， rejected
 * 状态只能从 pending -> fulfilled，pending -> rejected, 并且不可逆
 * resolve 将状态从 pending 变成fulfilled， reject将 pending 变成 rejected
 * resolve 由外部执行，并传入对应成功的值，先将 MyPromise 的状态改成 fulfilled ，再将值保存在成员属性 value 中，方便 then 函数 中使用
 * reject 由外部执行，并传入对应失败的值，先将 MyPromise 的状态改成rejected， 再讲值保存在成员属性 reason 中，方便 then 方式中获取
 * promise内部错误捕获，将错误通过reject往后传递过then函数
 */

 const PENDING = 'pending'
 const FULFILLED = 'fulfilled'
 const REJECTED = 'rejected'

class MyPromise {
  // promise状态
  status = PENDING
  // 成功的值
  value = undefined
  // 失败的值
  reason = undefined
  // 成功的回调
  successCallbacks = []
  // 失败的回调
  failCallbacks = []
  constructor(executor) {
    try {
      executor(this.resolve, this.reject)
    } catch (err) {
      this.reject(new Error(err))
    }
  }
  resolve = value => { // 使用箭头函数， 保留上下文
    // 状态已经变成rejected， 不可再更改
    if (this.status !== PENDING) return
    this.status = FULFILLED
    this.value = value
    // successCallback 默认的 undefined, 在 then 方法被调用的时候，如果 status 是 pending，才会被赋值, then 方法被调用多次，则每次存的 successCallback 会依次执行
    while (this.successCallbacks.length) this.successCallbacks.shift()()
  }
  reject = reason => { // 使用箭头函数， 保留上下文
    // 状态已经变成fulfilled， 不可再更改
    if (this.status !== PENDING) return
    this.status = REJECTED
    this.reason = reason
    // failCallback 默认的 undefined, 在 then 方法被调用的时候，如果 status 是 pending， 才会被赋值， then 方法被调用多次，则每次存的 failCallback 会依次执行
    while (this.failCallbacks.length) this.failCallbacks.shift()()
  }
  isFunc (value) {
    return value instanceof Function
  }
  /**
   * 接受两个成功，成功的回调，失败的回调
   *   * 1.
     * 先判断状态来执行对应的回调函数
     * 如果 status === FULFILLED, 调用成功的回调，并传入对应的value
     * 如果 status === REJECTED， 调用失败的回调，并传入对应的reason
     * 如果 status === PENDING, 证明 executor 里面有异步的逻辑，先将successCallback, failCallback保存在成员变量中，等异步逻辑执行完了之后再调用， 异步逻辑什么时候执行完？
     * 外部控制，异步逻辑执行完成之后，再调用 resolve 或者reject，所以 successCallback 可以在resolve 中调用，, failCallback 可以在 reject 中调用
     * 
     * 2.
     * 同一个的MyPromise的实例可以多次调用then方法，
     * executor 里面的代码是同步的，则会马上处理对应的逻辑
     * 但是 executor 里面的代码有异步逻辑，则需要保存多个 successCallback 和 failCallback
     * 
     * 3. then方法可以返回一个新的MyPromise实例，从而实现then方法的链式调用
   * @param {} successCallback 
   * @param {*} failCallback 
   */
  then (successCallback, failCallback) {
    // 先判断 successCallback, failCallback 是否存在并且是一个函数，如果不是，就复制一个默认函数，这个默认函数会将value或者reason继续向下传递
    successCallback = successCallback ? successCallback : value => value
    failCallback = failCallback ? failCallback : reason => {
      throw reason
    }
    const promise2 = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        setTimeout(() => {
          try {
            const result = successCallback(this.value)
            resolvePromise(promise2, result, resolve, reject)
          } catch (err) {
            reject(err)
          }
        }, 0)
      } else if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            const result = failCallback(this.reason)
            resolvePromise(promise2, result, resolve, reject)
          } catch (err) {
            reject(err)
          }
        }, 0)
      } else {
        this.successCallbacks.push(() => {
          setTimeout(() => {
            try {
              const result = successCallback(this.value)
              resolvePromise(promise2, result, resolve, reject)
            } catch (err) {
              reject(err)
            }
          }, 0)
        })
        this.failCallbacks.push(() => {
          setTimeout(() => {
            try {
              const result = failCallback(this.reason)
              resolvePromise(promise2, result, resolve, reject)
            } catch (err) {
              reject(new Error(err))
            }
          }, 0)
        })
      }
    })
    return promise2
  }
  /**
   * 接受一个回调函数, 这个回调在MyPromise实例有结果了才能调用
   * 返回一个新MyPromiseA实例， 并将上一个MyPromise的状态传递出去
   * callback 的返回值如果是一个普通值，在传递给下一个then的resolve
   * callback 的返回值如果是有个MyPromiseB，则必须要这个MyPromiseB执行完之后，MyPromiseA的then才能执行
   * @param {*} callback 
   */
  finally(callback) {
    return this.then(value => {
      return MyPromise.resolve(callback()).then(() => value)
    }, reason => {
      return MyPromise.resolve(callback()).then(() => {
        throw reason
      })
    })
  }
  /**
   * 只接受一个失败的回调
   * @param {*} failCallback 
   */
  catch(failCallback) {
    return this.then(undefined, failCallback)
  }
  /**
   * 是一个静态方法，可以通过MyPromise直接调用
   * 接受一个数组， 返回一个新的MyPromise对象
   * 数组中的值可能是MyPromise对应，可能是普通值
   * 如果是MyPromise对象，则等到then执行的时候，将MyPromise的value push到result中
   * 如果是普通值，则直接push到result中
   * 当result.lenth === array.length,则调用resolve，并将result传递出去
   * 当执行其中的某一个Mypromise出现错误，直接调用reject，并将reason抛出去，MyPromise触发失败的回调
   * @param {*} array 
   */
  static all (array) {
    return new MyPromise((resolve, reject) => {
      const result = []
      function addData(i, data) {
        result[i] = data
        if (result.length === array.length) {
          resolve(result)
        }
      }
      for (let i = 0 ; i < array.length; i++) {
        const current = array[i]
        if (current instanceof MyPromise) {
          current.then(value => addData(i, value), reject)
        } else {
          addData(i, current)
        }
      }
    })
  }
  /**
   * 是一个静态方法
   * 返回一个MyPromise实例
   * 接受一个参数，这个参数如果是MyPromise实例，则原封不动的返回，
   * 如果是一个普通值，则重新创建一个MyPromise实例，并立马调用resolve，将这个值传递出去
   * @param {*} value 
   */
  static resolve (value) {
    if (value instanceof MyPromise) return value
    return new MyPromise((resolve, reject) => {
      resolve(value)
    })
  }
}

function resolvePromise(promise2, result, resolve, reject) {
  /**
   * result 有两种情况，如果result是一个MyPromise实例，
   * 如果 MyPromise实例不是新创建的而是自己本身，会出现死循环的问题，需要抛出一个错误传递给下一个then函数
   * 否则等到这个实例的then方法调用完之后，在调用 resolve和reject
   * 如果result是一个普通的直接调用resolve，并传递上一个then函数返回的值
   */
  if (promise2 === result) {
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
  }
  if (result instanceof MyPromise) {
    // this.then(value => resolve(value), reason => reject(reason))
    result.then(resolve, reject)
  } else {
    resolve(result)
  }
}
module.exports = MyPromise